package cn.omisheep.autt.core.browser;

import cn.omisheep.autt.*;
import cn.omisheep.autt.core.ContentType;
import cn.omisheep.autt.core.Cookies;
import cn.omisheep.autt.core.Method;
import cn.omisheep.autt.core.RequestHeader;
import cn.omisheep.commons.util.InputStreamCacher;
import cn.omisheep.commons.util.charset.detector.CharsetDetector;
import cn.omisheep.commons.util.web.ConvertUtil;
import cn.omisheep.commons.util.web.URLParser;
import org.apache.commons.lang.builder.ToStringBuilder;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static cn.omisheep.commons.util.web.ConvertUtil.objectToForm;
import static cn.omisheep.commons.util.web.ConvertUtil.objectToJSON;


public class BrowserRequest implements GetRequest, PostRequest {

    private       int                 connTimeout;
    private       int                 readTimeout;
    private       Charset             charset;
    private       Object              body;
    private final Method              method;
    private final String              domain;
    private final String              uri;
    private final String              path;
    private final Map<String, String> param   = new HashMap<>();
    private final RequestHeader       header  = new RequestHeader();
    private final Cookies             cookies = new Cookies();
    private final BrowserConnect      connect;

    BrowserRequest(BrowserConnect connect,
                   String url,
                   Method method) {
        this.method  = method;
        this.connect = connect;
        URLParser.URLInfo urlInfo = URLParser.parse(url);
        this.domain = urlInfo.getDomain();
        this.uri    = urlInfo.getUri();
        this.path   = urlInfo.getPath();
        this.param.putAll(urlInfo.getParam());
        List<Cookies> cookies = connect.getCookiesListByUri(this.domain, this.uri);
        if (cookies != null) {
            this.cookies.putList(cookies);
        }
        this.connTimeout = connect.connTimeout;
        this.readTimeout = connect.readTimeout;
    }

    @Override
    public Response send() {
        HttpURLConnection conn = null;
        try {
            conn = requestConfig();
            List<String> list = conn.getHeaderFields().get("Set-Cookie");
            Cookies      coo  = new Cookies();
            if (list != null) {
                for (String s : list) {
                    Cookies  c       = new Cookies();
                    String[] split   = s.replaceAll(" ", "").split(";");
                    String   _domain = domain;
                    String   _path   = uri;
                    for (int i = 0; i < split.length; i++) {
                        String[] s1 = split[i].split("=");
                        if (i == 0) {
                            c.put(s1[0], s1[1]);
                            continue;
                        }
                        if (s1[0].equals("Domain")) {
                            _domain = s1[1];
                            continue;
                        }
                        if (s1[0].equals("Path")) {
                            _path = s1[1];
                        }
                    }
                    coo.putAll(c);
                    this.connect.setCookie(_domain, _path, c);
                }
            }
            return new BrowserResponse(this.connect, conn.getHeaderFields(), readResponseBody(conn), coo,
                                       conn.getResponseCode());
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            if (conn != null) {
                conn.disconnect();
            }
        }
    }

    @Override
    public Connect getConnect() {
        return this.connect;
    }

    @Override
    public Request cookie(String key,
                          String val) {
        this.cookies.put(key, val);
        return this;
    }

    @Override
    public Request cookies(Cookies cookies) {
        this.cookies.putAll(cookies);
        return this;
    }

    @Override
    public Request header(String key,
                          String val) {
        header.put(key, val);
        return this;
    }

    @Override
    public Request header(Map<String, String> header) {
        this.header.putAll(header);
        return this;
    }

    @Override
    public Request userAgent(String userAgent) {
        return header(RequestHeader.USER_AGENT, userAgent);
    }

    @Override
    public PostRequest body(Object o,
                            ContentType contentType) {
        this.body = o;
        return contentType(contentType);
    }

    @Override
    public PostRequest body(Object o) {
        this.body = o;
        return this;
    }

    @Override
    public PostRequest contentType(ContentType contentType) {
        header.setContentType(contentType);
        return this;
    }

    @Override
    public GetRequest param(Object param) {
        if (param == null) return this;
        ConvertUtil.objectToMap(param, this.param);
        return this;
    }

    @Override
    public GetRequest param(String key,
                            Object val) {
        if (val != null) param.put(key, val.toString());
        else param.put(key, "");
        return this;
    }

    @Override
    public Request connTimeout(int connTimeout) {
        this.connTimeout = connTimeout;
        return this;
    }

    @Override
    public Request readTimeout(int readTimeout) {
        this.readTimeout = readTimeout;
        return this;
    }

    @Override
    public Request charset(String charset) {
        this.charset = Charset.forName(charset);
        return this;
    }

    @Override
    public Request charset(Charset charset) {
        this.charset = charset;
        return this;
    }

    @Override
    public String toString() {
        return new ToStringBuilder(this)
                .append("connTimeout", connTimeout)
                .append("readTimeout", readTimeout)
                .append("charset", charset)
                .append("body", body)
                .append("method", method)
                .append("domain", domain)
                .append("uri", uri)
                .append("path", path)
                .append("param", param)
                .append("header", header)
                .append("cookies", cookies)
                .toString();
    }

    private HttpURLConnection openConnection() throws IOException {
        URL realUrl = new URL(getURL());
        return (HttpURLConnection) realUrl.openConnection();
    }

    private HttpURLConnection requestConfig() throws IOException {
        HttpURLConnection conn = openConnection();
        conn.setConnectTimeout(connTimeout);
        conn.setReadTimeout(readTimeout);

        parseContentType();

        for (Map.Entry<String, String> entry : header.entrySet()) {
            conn.setRequestProperty(entry.getKey(), entry.getValue());
        }
        conn.setRequestProperty("Cookie", cookies.toPropertyString());

        conn.setRequestMethod(method.name());
        if (method.hasBody()) {
            String _body = parseBody();
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setRequestProperty("Content-Length", _body.length() + "");
            PrintWriter out = new PrintWriter(conn.getOutputStream());
            out.print(_body);
            out.flush();
            out.close();
        }
        return conn;
    }

    private void parseContentType() {
        String contentTypeStr = header
                .getOrDefault("Content-type",
                              header.getOrDefault("Content-Type",
                                                  header.getOrDefault(
                                                          "content-type",
                                                          null)));
        if (contentTypeStr == null) {
            return;
        }
        for (String cc : contentTypeStr.split(";")) {
            if (cc.startsWith("charset")) {
                charset = Charset.forName(cc.split("=")[1]);
            } else if (ContentType.JSON.getCt().equals(cc)) {
                header.setContentType(ContentType.JSON);
            } else if (ContentType.X_WWW_FORM_URLENCODED.getCt().equals(cc)) {
                header.setContentType(ContentType.X_WWW_FORM_URLENCODED);
            }
        }

        if (header.getContentType() != null) {
            String type = header.getContentType().getCt();
            if (this.charset != null) {
                type += ";charset=" + this.charset;
            }
            header.put("Content-Type", type);
        }
    }

    private ResultObject readResponseBody(HttpURLConnection conn) throws IOException {
        if (conn == null) return new ResultObject();
        InputStreamCacher inputStreamCacher;
        try {
            inputStreamCacher = new InputStreamCacher(conn.getInputStream());
        } catch (IOException e) {
            inputStreamCacher = new InputStreamCacher(conn.getErrorStream());
        }

        List<String> li       = conn.getHeaderFields().get("Content-Type");
        Charset      _charset = null;
        if (li != null) {
            String[] split = li.get(0).split(";");
            for (String v : split) {
                String _v = v.trim();
                if (_v.startsWith("charset")) {
                    _charset = Charset.forName(_v.split("=")[1]);
                }
            }
        }

        if (_charset == null) {
            InputStream charsetInputStream = inputStreamCacher.getInputStream();
            _charset = CharsetDetector.detect(charsetInputStream);
            charsetInputStream.close();
        }

        InputStream in = inputStreamCacher.getInputStream();

        byte[] buffer = new byte[512];
        int    len;

        ByteArrayOutputStream bios = new ByteArrayOutputStream();
        while ((len = in.read(buffer)) > -1) {
            bios.write(buffer, 0, len);
        }

        return new ResultObject(_charset, bios.toByteArray());
    }

    private String parseBody() {
        if (this.body == null) {
            return "";
        }

        if (header.getContentType() == null) {
            try {
                throw new NullPointerException("Content-Type is null");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        if (this.body instanceof String) {
            return (String) this.body;
        }

        try {
            if (header.getContentType().equals(ContentType.JSON)) {
                return objectToJSON(this.body);
            } else if (header.getContentType().equals(ContentType.X_WWW_FORM_URLENCODED)) {
                return objectToForm(this.body);
            } else {
                throw new Exception("content-type mismatch");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }

    private String getURL() {
        if (param.isEmpty()) return path;
        return path + "?" + objectToForm(param);
    }

}
